using System;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Entity.Scene;

namespace SmallVCM
{
    using Ray = IRay;
    public struct ComponentProbabilities
    {
        public float diffProb;
        public float phongProb;
        public float reflProb;
        public float refrProb;
    };

    [Flags]
    public enum Events
    {
        kNONE = 0,
        kDiffuse = 1,
        kPhong = 2,
        kReflect = 4,
        kRefract = 8,
        kSpecular = (kReflect | kRefract),
        kNonSpecular = (kDiffuse | kPhong),
        kAll = (kSpecular | kNonSpecular)
    };

    public class Bsdf
    {
        int mMaterialID;       //!< Id of scene material, < 0 ~ invalid
        protected ONB mFrame;            //!< Local frame of reference
        Vector mLocalDirFix;      //!< Incoming (fixed) direction, in local
        bool mIsDelta;          //!< True when material is purely specular
        protected ComponentProbabilities mProbabilities; //!< Sampling probabilities
        float mContinuationProb; //!< Russian roulette probability
        float mReflectCoeff;     //!< Fresnel reflection coefficient (for glass) 

        Bsdf(
        Ray aRay,
        ref Normal normal,
        Material Material,
        SceneGeometryInfo aScene)
        {
            Setup(aRay, ref normal, Material, aScene);
        }

        void Setup(
            Ray aRay,
            ref Normal normal,
            Material Material,
            SceneGeometryInfo aScene)
        {
            mMaterialID = -1;
            mFrame.SetFromZ(ref normal);
            var invdir = -aRay.Direction;
            mLocalDirFix = mFrame.ToLocal(ref invdir);

            // reject rays that are too parallel with tangent plane
            if (Math.Abs(mLocalDirFix.z) < Consts.EPS_COSINE)
            {
                return;
            }

            GetComponentProbabilities(Material, mProbabilities);

            mIsDelta = (Math.Abs(mProbabilities.diffProb) < MathLab.Epsilon) && (Math.Abs(mProbabilities.phongProb) < MathLab.Epsilon);

            // now it becomes valid
            mMaterialID = 1;//Material.mIOR;
        }

        public bool IsValid() { return mMaterialID >= 0; }
        public bool IsDelta() { return mIsDelta; }
        public float ContinuationProb() { return mContinuationProb; }
        public float CosThetaFix() { return mLocalDirFix.z; }
        public Vector WorldDirFix() { return mFrame.ToWorld(ref mLocalDirFix); }

        float AlbedoDiffuse(Material aMaterial)
        {
            return Luminance(aMaterial.mDiffuseReflectance);
        }

        private float Luminance(RgbSpectrum mDiffuseReflectance)
        {
            return (mDiffuseReflectance).y();
        }

        float AlbedoPhong(Material aMaterial)
        {
            return Luminance(aMaterial.mPhongReflectance);
        }

        float AlbedoReflect(Material aMaterial)
        {
            return Luminance(aMaterial.mMirrorReflectance);
        }

        float AlbedoRefract(Material aMaterial)
        {
            return 1f;
        }

        void GetComponentProbabilities(
             Material aMaterial,
            ComponentProbabilities oProbabilities)
        {
            mReflectCoeff = Utils.FresnelDielectric(mLocalDirFix.z, aMaterial.mIOR);

            float albedoDiffuse = AlbedoDiffuse(aMaterial);
            float albedoPhong = AlbedoPhong(aMaterial);
            float albedoReflect = mReflectCoeff * AlbedoReflect(aMaterial);
            float albedoRefract = (1f - mReflectCoeff) * AlbedoRefract(aMaterial);

            float totalAlbedo = albedoDiffuse + albedoPhong + albedoReflect + albedoRefract;

            if (totalAlbedo < 1e-9f)
            {
                oProbabilities.diffProb = 0f;
                oProbabilities.phongProb = 0f;
                oProbabilities.reflProb = 0f;
                oProbabilities.refrProb = 0f;
                mContinuationProb = 0f;
            }
            else
            {
                oProbabilities.diffProb = albedoDiffuse / totalAlbedo;
                oProbabilities.phongProb = albedoPhong / totalAlbedo;
                oProbabilities.reflProb = albedoReflect / totalAlbedo;
                oProbabilities.refrProb = albedoRefract / totalAlbedo;
                // The continuation probability is max component from reflectance.
                // That way the weight of sample will never rise.
                // Luminance is another very valid option.
                mContinuationProb =
                    (aMaterial.mDiffuseReflectance +
                    aMaterial.mPhongReflectance +
                    mReflectCoeff * aMaterial.mMirrorReflectance).Max() +
                    (1f - mReflectCoeff);

                mContinuationProb = Math.Min(1f, Math.Max(0f, mContinuationProb));
            }
        }


    }
}